冰蝎v3.0 Beta 2放出来没多久,就被找到了固定长度的特征,过两天又更新了,加了一些随机字符,固定长度特征消失。
哥斯拉也有一定的优点,比冰蝎好一点,但是也存在固定长度的问题
实验环境
控制端:win10 + 哥斯拉Godzilla V1.00
服务端:Ubuntu 16.04 + Apache + PHP 7.0.33
以php为例的webshell分析
php的webshell有两种,一种是php_xor_base64,另一种是php_xor_raw
php_xor_base64
首先看php_xor_base64
1 | <?php |
密码是$P,也即pass
他是加密通信的,密钥生成shell的时候配置的,默认是key,md5后的前十六位,就是上面的$T='3c6e0b8a9c15224a';
,这个跟冰蝎v3.0的密钥格式是一致的。
1 | substr(md5('key')),0,16)=='3c6e0b8a9c15224a' |
这个代码单单这么看是看不出什么,$F是我们的输入,这有两种情况
1、不存在$_SESSION[$V]
,$F就赋值给$_SESSION[$V]
2、存在的时候就,$F是run函数的参数
你会发现在这个php中没有run函数,那就只能在调用run之前动态生成了,也就是下面几行代码
1 | $L=$_SESSION[$V]; |
可以看到存在$_SESSION[$V]
的时候,每次都会定义一个C类,里面只有一个函数nvoke,里面是直接执行eval
而后面新建一个C类,并调用利用的nvoke函数,参数是$A[0]
,$A[0]
来源于$_SESSION[$V]
经过|分割打散为数组的第一个元素(通过解密,你会发现$_SESSION[$V]
里面没有|字符,可能是作者为了以后添加更多功能而设计的,所以没有|的$_SESSION[$V]
,跟$A[0]
的字符是一样的)
为了更清晰地了解整个过程,我们添加下面代码通过输出变量的方式快速获取实际执行的明文代码
1 | file_put_contents("/var/www/html/Godzilla/info.txt", $F , FILE_APPEND | LOCK_EX); |
通过这个我们得到了初次连接webshell的3次通信解密后的明文,整个过程如下图
第一次发送很多功能的PHP代码
下面是第一次发送解密后存在$_SESSION[$V]
中的代码,可以看第一个函数就是run函数了!
简单解析一下,在run函数里面,通过formatParameter函数将参数解析到全局变量$parameters中,最后执行evalFunc()执行对应的函数功能,最后用base64Encode返回结果
1 | $parameters=array(); |
我们单独把evalFunc拿出来看看,就是通过获取参数methodName的值,之后一般都是走调用$methodName()的路径
1 | function evalFunc(){ |
第二次调用test函数
服务端收到的解密结果是:
1 | methodName=dGVzdA== |
我们base64解码一下参数的值:
1 | methodName=test |
所以是调用上面的test函数,就是直接返回ok而已
1 | function test(){ |
第三次获取基本信息
服务端收到的解密结果是:
1 | methodName=Z2V0QmFzaWNzSW5mbw== |
我们base64解码一下参数的值:
1 | methodName=getBasicsInfo |
就是调用getBasicsInfo函数,那就是我们进入webshell后看到的服务器的一些基础信息了
1 | function getBasicsInfo() |
接下来说特征,由于有url编码,导致第一次长度变化很大
假如防护设备默认url解码的话,=号后面的base64字符长度是23068,但返回的长度是0,也是非常重要的特征
第一次长度变化很多我们可以检查第二次啊
第二次也是由于url编码导致请求变化,有url解码的话,=号后面的base64字符长度是40,返回包应该不会出现url编码,长度是40
header由于可以自定义,我们就不关注了,通过逆向看源码是从本地数据库读取的,就是用户设置存储到本地的
下面是通过通过JDGUI反编译得到的源码
1 | private static void initHttpHeader() { |
php_xor_raw
看完base64的,我们看看php_xor_raw,还是先看webshell文件
1 | <?php |
可以看到这里把密码的判断删除了,直接从php://input
接收输入(毕竟有不可见字符)
php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。
流程就不重复说了,跟上面的是一样的
因为这个不存url编码的问题,所以长度是固定的,第一次的HTTP回应包的body也是0,这个比base64更容易检测
前两次的请求长度和响应长度分别如下:
1 | 17300 0 |